package com.icee.myth.gm;

import com.icee.myth.gm.activity.*;
import com.icee.myth.gm.admin.UserData;
import com.icee.myth.gm.api.OAuthHelper;
import com.icee.myth.gm.client.GameServerClient;
import com.icee.myth.gm.config.GmConfigManager;
import com.icee.myth.gm.config.ServerConfig;
import com.icee.myth.gm.db.DbHandler;
import com.icee.myth.gm.kf.JsonKFTemplate;
import com.icee.myth.gm.kf.JsonKFTemplates;
import com.icee.myth.gm.kf.KF;
import com.icee.myth.gm.kf.KFStaticInfo;
import com.icee.myth.gm.message.AddPostMessage;
import com.icee.myth.gm.message.DeletePostMessage;
import com.icee.myth.gm.message.Message;
import com.icee.myth.gm.pipelineFactory.HttpServerToGwPipelineInitializer;
import com.icee.myth.utils.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import javax.activation.MimetypesFileTypeMap;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author yangyi
 */
public class GmServer implements Runnable {
    public MimetypesFileTypeMap mimeTypesMap;
    public String dbHost;  // 数据库地址
    public String dbName;  // 数据库名
    public String managerDbHost;  // 数据库地址
    public String managerDbName;  // 数据库名
    public String queryPlatformUidUrl = "http://s1.bo.37wanwan.com/gbs/internalapi/user.queryPlatformUid"; //根据本地userid查询第三方platform_uid
    public String queryUseridUrl = "http://s1.bo.37wanwan.com/gbs/internalapi/user.queryUserid";          //根据第三方platform_uid查询本地userid
    public String puidKey = "S1-WAN37";
    public String puidSecretkey = "373737777";
    public String platform;
    public OAuthHelper oAuthHelper;

    public ConcurrentLinkedQueue<Message> messageQueue = new ConcurrentLinkedQueue<Message>();
    private final HashMap<Integer, UserData> sessionId2UserDataMap = new HashMap<Integer, UserData>();
    private final HashMap<String, Integer> userName2SessionIdMap = new HashMap<String, Integer>();

    private ConcurrentHashMap<Integer, GameServerClient> clients;
    private HashSet<String> trustedBrowserHosts = new HashSet<String>();
    private int serverPort;
    private final LinkedList<Activity> activitys = new LinkedList<Activity>();
    private final LinkedList<KF> kfs = new LinkedList<KF>();
    public JsonKFTemplates kfTemplates;

    public static GmServer INSTANCE = new GmServer();

    private GmServer() {
        clients = new ConcurrentHashMap<Integer, GameServerClient>();
    }

    private boolean init() {
        try {
            mimeTypesMap = new MimetypesFileTypeMap(System.getProperty("user.dir") + "/" + "public/mime.types");
        } catch (Exception e) {
            MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, "Can't init mime types" + StackTraceUtil.getStackTrace(e));
            return false;
        }

        DbHandler dbHandler = new DbHandler(dbHost, dbName);
        if (!dbHandler.connectToDB()) {
            MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, "Can't connect to database[" + dbHost + ":" + dbName + "].");
            dbHandler.close();
            return false;
        }

        if (!initTrustedBrowserHosts(dbHandler)) {
            MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, "Can't get trusted browser hosts.");
            dbHandler.close();
            return false;
        }

        if (!initServers()) {
            MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, "Can't init servers.");
            dbHandler.close();
            return false;
        }

        dbHandler.close();
        return true;
    }

    private void config() {
//        ManagerConfigManager.loadManagerConfig(Consts.SERVERS_CONFIG_FILEPATH, this);
        GmConfigManager.loadActivityConfig(Consts.ACTIVITIES_CONFIG_FILEPATH, this);
        GmConfigManager.loadKFConfig(Consts.KF_CONFIG_FILEPATH, this);
    }

    private void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();

        bootstrap.group(bossGroup, workerGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.SO_REUSEADDR, true);

        // Set up the event pipeline factory.
        bootstrap.childHandler(new HttpServerToGwPipelineInitializer());

        // Bind and start to accept incoming connections.
        InetSocketAddress isa = new InetSocketAddress(serverPort);

        try {
            ChannelFuture f = bootstrap.bind(isa).sync();
            System.out.println("gm server bind ok, InetSocketAddress=" + isa.toString());

            new Thread(this, "GmThread").start();
            System.out.println("GmThread start");

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public boolean isTrustedBrowser(String browserIP) {
        return true;
    }

    public void addUserData(UserData userData) {
        synchronized (this) {
            Integer oldSessionId = userName2SessionIdMap.remove(userData.userName);
            if (oldSessionId != null) {
                sessionId2UserDataMap.remove(oldSessionId);
            }

            int sessionId;
            do {
                sessionId = RandomGenerator.INSTANCE.generator.nextInt(Integer.MAX_VALUE);
            } while (sessionId2UserDataMap.containsKey(sessionId));

            userData.sessionId = sessionId;
            userData.sessionExpireTime = System.currentTimeMillis() + Consts.SESSION_EXPIRE_TIME;

            sessionId2UserDataMap.put(sessionId, userData);
            userName2SessionIdMap.put(userData.userName, sessionId);
        }
    }

    public UserData getUserData(int sessionId) {
        synchronized (this) {
            UserData userData = sessionId2UserDataMap.get(sessionId);
            if (userData != null) {
                if (System.currentTimeMillis() < userData.sessionExpireTime) {
                    return userData;
                }
                sessionId2UserDataMap.remove(sessionId);
                userName2SessionIdMap.remove(userData.userName);
            }
            return null;
        }
    }

    public GameServerClient getServer(int serverId) {
        return clients.get(serverId);
    }

    public ConcurrentHashMap<Integer, GameServerClient> getGameServerClients() {
        return clients;
    }

    public boolean removeUserDate(String userName) {
        synchronized (this) {
            Integer sessionId = userName2SessionIdMap.get(userName);
            if (sessionId != null) {
                sessionId2UserDataMap.remove(sessionId);
                userName2SessionIdMap.remove(userName);
                return true;
            }
            return false;
        }
    }

    private boolean parseArgs(String[] args) {
        // parse private host
        String dbHostString = parseArg(args, "-dbHost");
        if (dbHostString != null) {
            if (dbHostString.compareTo("localhost") == 0 || CommonUtil.ValidateIPAddress(dbHostString)) {
                dbHost = dbHostString;
            } else {
                System.err.println("Value of \"-dbHost\" param must be a ip address.");
                return false;
            }
        } else {
            System.err.println("Lack \"-dbHost XXX\" param");
            return false;
        }

        // parse private host
        dbName = parseArg(args, "-dbName");
        if (dbName == null) {
            System.err.println("Lack \"-dbName XXX\" param");
            return false;
        }

        String managerDbHostString = parseArg(args, "-dbManagerHost");
        if (managerDbHostString != null) {
            if (managerDbHostString.compareTo("localhost") == 0 || CommonUtil.ValidateIPAddress(managerDbHostString)) {
                managerDbHost = managerDbHostString;
            } else {
                System.err.println("Value of \"-managerDbHost\" param must be a ip address.");
                return false;
            }
        } else {
            System.err.println("Lack \"-managerDbHost XXX\" param");
            return false;
        }

        // parse private host
        managerDbName = parseArg(args, "-managerDbName");
        if (managerDbName == null) {
            System.err.println("Lack \"-managerDbName XXX\" param");
            return false;
        }

        String serverPortString = parseArg(args, "-port");
        if (serverPortString == null) {
            System.err.println("Lack \"-port XXX\" param");
            return false;
        } else {
            serverPort = Integer.parseInt(serverPortString);
        }

        queryPlatformUidUrl = parseArg(args, "-queryPlatformUidUrl");
        if (queryPlatformUidUrl == null) {
            System.err.println("Lack \"-queryPlatformUidUrl XXX\" param");
            return false;
        }

        queryUseridUrl = parseArg(args, "-queryUseridUrl");
        if (queryUseridUrl == null) {
            System.err.println("Lack \"-queryUseridUrl XXX\" param");
            return false;
        }

        puidKey = parseArg(args, "-puidKey");
        if (puidKey == null) {
            System.err.println("Lack \"-puidKey XXX\" param");
            return false;
        }

        puidSecretkey = parseArg(args, "-puidSecretkey");
        if (puidSecretkey == null) {
            System.err.println("Lack \"-puidSecretkey XXX\" param");
            return false;
        }

        oAuthHelper = new OAuthHelper(puidKey, "", puidSecretkey, "", "", "", "");

        platform = parseArg(args, "-platform");
        if (platform == null) {
            System.err.println("Lack \"-platform XXX\" param");
            return false;
        }

        return true;
    }

    private String parseArg(String[] args, String param) {
        int argsNum = args.length;
        String retVal = null;
        for (int i = 0; i < argsNum; i++) {
            if (args[i].compareTo(param) == 0) {
                if (i + 1 < argsNum) {
                    retVal = args[i + 1];
                }
                break;
            }
        }

        return retVal;
    }

    private boolean initTrustedBrowserHosts(DbHandler dbHandler) {
        trustedBrowserHosts = dbHandler.getTrustedBrowserHosts();

        return !trustedBrowserHosts.isEmpty();
    }

    private boolean initServers() {
        DbHandler managerDbHandler = new DbHandler(managerDbHost, managerDbName);
        LinkedList<ServerConfig> serverConfigs = managerDbHandler.getServerConfigs();
        managerDbHandler.close();
        if (serverConfigs.isEmpty()) {
            return false;
        }

        initServers(serverConfigs);
        return true;
    }

    private void initServers(LinkedList<ServerConfig> serverConfigs) {
        clients = new ConcurrentHashMap<Integer, GameServerClient>();

        for (ServerConfig serverConfig : serverConfigs) {
            GameServerClient gameServerClient = new GameServerClient(serverConfig);
            clients.put(gameServerClient.serverConfig.id, gameServerClient);
        }
    }

    /*
     * 启动参数：
     * -ea -Dfile.encoding=UTF-8 -classpath dist/ProjectKGmServer.jar com.icee.myth.gm.GmServer -dbHost 192.168.0.150 -dbName gmserver -dbManagerHost 192.168.0.150 -managerDbName sanguo_manager -port 8081 -queryPlatformUidUrl "http://s1.bo.37wanwan.com/gbs/internalapi/user.queryPlatformUid" -queryUseridUrl "http://s1.bo.37wanwan.com/gbs/internalapi/user.queryUserid" -puidKey "S1-WAN37" -puidSecretkey "373737777" -platform "wan360"
     * */

    public static void main(String[] args) {
        if (args[0].compareTo("-version") == 0) {
            System.out.println("GmServer version : " + Version.getBuildVersion());
            return;
        }

        // initialize logger
        MLogger.init("manager", LogConsts.LOGFLUSH_INTERVAL);

        // 从配置文件读取配置信息
        if (!GmServer.INSTANCE.parseArgs(args)) {
            return;
        }

        if (!GmServer.INSTANCE.init()) {
            return;
        }
        GmServer.INSTANCE.config();
        GmServer.INSTANCE.start();
    }

    @Override
    public void run() {
        while (true) {
            try {
                handleMessage();
                // handle activity
                updateActivities();
                updateKF();
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, StackTraceUtil.getStackTrace(ex));
            }
        }
    }

    public void initActivities(JSONActivityTemplates activityTemplates) {
        if ((activityTemplates.templates != null) && (activityTemplates.templates.length > 0)) {
            for (JSONActivityTemplate activityTemplate : activityTemplates.templates) {
                ActivityStaticInfo staticInfo = new ActivityStaticInfo(activityTemplate);
                addActivity(staticInfo);
            }
        }
    }

    public void initKFConfig(JsonKFTemplates kfTemplates) {
        kfs.clear();
        this.kfTemplates = kfTemplates;
        if (kfTemplates != null && (kfTemplates.templates != null) && (kfTemplates.templates.size() > 0)) {
            for (JsonKFTemplate kfTemplate : kfTemplates.templates) {
                KFStaticInfo staticInfo = new KFStaticInfo(kfTemplate);
                addKF(staticInfo);
            }
        }
    }

    private void addActivity(ActivityStaticInfo staticInfo) {
        GameServerClient server = getServer(staticInfo.serverId);
        if (server != null) {
            activitys.add(new Activity(server, staticInfo, System.currentTimeMillis()));
        } else {
            MLogger.getlogger().debuglog(LogConsts.LOGLEVEL_ERROR, "Can't create activity because server[" + staticInfo.serverId + "] not exist.");
        }
    }


    private void addKF(KFStaticInfo staticInfo) {
        kfs.add(new KF(staticInfo, System.currentTimeMillis()));
    }

    public void updateActivities() {
        long currentTime = System.currentTimeMillis();

        for (Iterator<Activity> it = activitys.iterator(); it.hasNext(); ) {
            Activity activity = it.next();
            if (!activity.update(currentTime)) {
                it.remove();
            }
        }
    }

    public void updateKF() {
        long currentTime = System.currentTimeMillis();

        for (Iterator<KF> it = kfs.iterator(); it.hasNext(); ) {
            KF kf = it.next();
            if (!kf.update(currentTime)) {
                it.remove();
            }
        }
    }

    private void handleMessage() {
        Message msg = messageQueue.poll();
        while (msg != null) {
            switch (msg.getType()) {
                case ADDPOST: {
                    AddPostMessage addPostMessage = (AddPostMessage) msg;
                    if (kfTemplates == null) {
                        kfTemplates = new JsonKFTemplates();
                    }
                    int id = 0;
                    if (kfTemplates.templates.size() > 0) {
                        JsonKFTemplate template = kfTemplates.templates.get(kfTemplates.templates.size() - 1);
                        id = template.id + 1;
                    }
                    kfTemplates.templates.add(new JsonKFTemplate(id, addPostMessage.serverId, new JSONBroadcastInfo(addPostMessage.startTime, addPostMessage.endTime, addPostMessage.period, Escape.unescape(addPostMessage.message))));
                    initKFConfig(kfTemplates);
                    writeKFTemplate();
                    break;
                }
                case DELETEPOST: {
                    if (kfTemplates != null) {
                        DeletePostMessage deletePostMessage = (DeletePostMessage) msg;
                        for (Iterator<JsonKFTemplate> it = kfTemplates.templates.iterator(); it.hasNext(); ) {
                            JsonKFTemplate template = it.next();
                            if (template.id == deletePostMessage.id) {
                                it.remove();
                                initKFConfig(kfTemplates);
                                writeKFTemplate();
                                break;
                            }
                        }
                    }
                    break;
                }
            }
            msg = messageQueue.poll();
        }
    }

    private void writeKFTemplate() {
        BufferedWriter bw = null;
        try {
            FileWriter fw = new FileWriter(Consts.KF_CONFIG_FILEPATH);
            bw = new BufferedWriter(fw);
            bw.write(JSONHelper.toJSON(kfTemplates));
        } catch (IOException ex) {
            Logger.getLogger(GmServer.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            if (null != bw) {
                try {
                    bw.close();
                    bw = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
